/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.physics.box2d;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Shape.Type;
import com.badlogic.gdx.utils.GdxRuntimeException;

public class Fixture {
	/*JNI
#include <Box2D/Box2D.h>
	 */
    /**
     * Get the contact filtering data.
     */
    private final short[] tmp = new short[3];
    private final Filter filter = new Filter();
    /**
     * the address of the fixture
     **/
    protected long addr;
    /**
     * the shape, initialized lazy
     **/
    protected Shape shape;
    /**
     * user specified data
     **/
    protected Object userData;
    /**
     * body
     **/
    private Body body;

    /**
     * Constructs a new fixture
     *
     * @param addr the address of the fixture
     */
    protected Fixture(Body body, long addr) {
        this.body = body;
        this.addr = addr;
    }

    protected void reset(Body body, long addr) {
        this.body = body;
        this.addr = addr;
        this.shape = null;
        this.userData = null;
    }

    /**
     * Get the type of the child shape. You can use this to down cast to the concrete shape.
     *
     * @return the shape type.
     */
    public Type getType() {
        int type = jniGetType(addr);
        switch (type) {
            case 0:
                return Type.Circle;
            case 1:
                return Type.Edge;
            case 2:
                return Type.Polygon;
            case 3:
                return Type.Chain;
            default:
                throw new GdxRuntimeException("Unknown shape type!");
        }
    }

    private native int jniGetType(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		b2Shape::Type type = fixture->GetType();
		switch( type )
		{
		case b2Shape::e_circle: return 0;
		case b2Shape::e_edge: return 1;
		case b2Shape::e_polygon: return 2;
		case b2Shape::e_chain: return 3;
		default:
			return -1;
		}
	*/

    /**
     * Returns the shape of this fixture
     */
    public Shape getShape() {
        if (shape == null) {
            long shapeAddr = jniGetShape(addr);
            if (shapeAddr == 0) throw new GdxRuntimeException("Null shape address!");
            int type = Shape.jniGetType(shapeAddr);

            switch (type) {
                case 0:
                    shape = new CircleShape(shapeAddr);
                    break;
                case 1:
                    shape = new EdgeShape(shapeAddr);
                    break;
                case 2:
                    shape = new PolygonShape(shapeAddr);
                    break;
                case 3:
                    shape = new ChainShape(shapeAddr);
                    break;
                default:
                    throw new GdxRuntimeException("Unknown shape type!");
            }
        }

        return shape;
    }

    private native long jniGetShape(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return (jlong)fixture->GetShape();
	*/

    private native void jniSetSensor(long addr, boolean sensor); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetSensor(sensor);
	*/

    /**
     * Is this fixture a sensor (non-solid)?
     *
     * @return the true if the shape is a sensor.
     */
    public boolean isSensor() {
        return jniIsSensor(addr);
    }

    /**
     * Set if this fixture is a sensor.
     */
    public void setSensor(boolean sensor) {
        jniSetSensor(addr, sensor);
    }

    private native boolean jniIsSensor(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->IsSensor();
	*/

    private native void jniSetFilterData(long addr, short categoryBits, short maskBits, short groupIndex); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		b2Filter filter;
		filter.categoryBits = categoryBits;
		filter.maskBits = maskBits;
		filter.groupIndex = groupIndex;
		fixture->SetFilterData(filter);
	*/

    public Filter getFilterData() {
        jniGetFilterData(addr, tmp);
        filter.maskBits = tmp[0];
        filter.categoryBits = tmp[1];
        filter.groupIndex = tmp[2];
        return filter;
    }

    /**
     * Set the contact filtering data. This will not update contacts until the next time step when either parent body is active and
     * awake. This automatically calls Refilter.
     */
    public void setFilterData(Filter filter) {
        jniSetFilterData(addr, filter.categoryBits, filter.maskBits, filter.groupIndex);
    }

    private native void jniGetFilterData(long addr, short[] filter); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		unsigned short* filterOut = (unsigned short*)filter;
		b2Filter f = fixture->GetFilterData();
		filterOut[0] = f.maskBits;
		filterOut[1] = f.categoryBits;
		filterOut[2] = f.groupIndex;
	*/

    /**
     * Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide.
     */
    public void refilter() {
        jniRefilter(addr);
    }

    private native void jniRefilter(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->Refilter();
	*/

    /**
     * Get the parent body of this fixture. This is NULL if the fixture is not attached.
     */
    public Body getBody() {
        return body;
    }

    /**
     * Test a point for containment in this fixture.
     *
     * @param p a point in world coordinates.
     */
    public boolean testPoint(Vector2 p) {
        return jniTestPoint(addr, p.x, p.y);
    }

    /**
     * Test a point for containment in this fixture.
     *
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    public boolean testPoint(float x, float y) {
        return jniTestPoint(addr, x, y);
    }

    private native boolean jniTestPoint(long addr, float x, float y); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->TestPoint( b2Vec2( x, y ) );
	*/

// const b2Body* GetBody() const;
//
// /// Get the next fixture in the parent body's fixture list.
// /// @return the next shape.
// b2Fixture* GetNext();
// const b2Fixture* GetNext() const;
//
// /// Get the user data that was assigned in the fixture definition. Use this to
// /// store your application specific data.
// void* GetUserData() const;
//
// /// Set the user data. Use this to store your application specific data.
// void SetUserData(void* data);
//
// /// Cast a ray against this shape.
// /// @param output the ray-cast results.
// /// @param input the ray-cast input parameters.
// bool RayCast(b2RayCastOutput* output, const b2RayCastInput& input) const;
//
// /// Get the mass data for this fixture. The mass data is based on the density and
// /// the shape. The rotational inertia is about the shape's origin. This operation
// /// may be expensive.
// void GetMassData(b2MassData* massData) const;

    private native void jniSetDensity(long addr, float density); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetDensity(density);
	*/

    /**
     * Get the density of this fixture.
     */
    public float getDensity() {
        return jniGetDensity(addr);
    }

    /**
     * Set the density of this fixture. This will _not_ automatically adjust the mass of the body. You must call
     * b2Body::ResetMassData to update the body's mass.
     */
    public void setDensity(float density) {
        jniSetDensity(addr, density);
    }

    private native float jniGetDensity(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->GetDensity();
	*/

    /**
     * Get the coefficient of friction.
     */
    public float getFriction() {
        return jniGetFriction(addr);
    }

    /**
     * Set the coefficient of friction.
     */
    public void setFriction(float friction) {
        jniSetFriction(addr, friction);
    }

    private native float jniGetFriction(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->GetFriction();
	*/

    private native void jniSetFriction(long addr, float friction); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetFriction(friction);
	*/

    /**
     * Get the coefficient of restitution.
     */
    public float getRestitution() {
        return jniGetRestitution(addr);
    }

    /**
     * Set the coefficient of restitution.
     */
    public void setRestitution(float restitution) {
        jniSetRestitution(addr, restitution);
    }

    private native float jniGetRestitution(long addr); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		return fixture->GetRestitution();
	*/

    private native void jniSetRestitution(long addr, float restitution); /*
		b2Fixture* fixture = (b2Fixture*)addr;
		fixture->SetRestitution(restitution);
	*/

// /// Get the fixture's AABB. This AABB may be enlarge and/or stale.
// /// If you need a more accurate AABB, compute it using the shape and
// /// the body transform.
// const b2AABB& GetAABB() const;

    /**
     * @return custom user data
     */
    public Object getUserData() {
        return userData;
    }

    /**
     * Sets custom user data.
     */
    public void setUserData(Object userData) {
        this.userData = userData;
    }
}
